package com.zsummer.baikal.common.config.security;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import com.alibaba.fastjson.JSON;
import com.zsummer.baikal.common.constant.Constants;
import com.zsummer.baikal.common.web.R;
import com.zsummer.baikal.system.entity.SysUser;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.log.LogMessage;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;

/**
 * 登录认证
 *
 * @author zsummer
 * @since 2021/7/28
 */
@Component
public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final TokenManager tokenManager;

    private final RedisTemplate redisTemplate;

    private final AuthenticationEntryPoint authenticationEntryPoint;

    /**
     * @param authenticationManager    添加@Lazy解决spring bean的相互依赖问题
     * @param tokenManager
     * @param redisTemplate
     * @param authenticationEntryPoint
     */
    public LoginAuthenticationFilter(@Lazy AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        String requestBody = IoUtil.read(request.getInputStream(), CharsetUtil.UTF_8);
        SysUser sysUser = JSON.parseObject(requestBody, SysUser.class);
        String username = sysUser.getUsername();
        username = (username != null) ? username : "";
        username = username.trim();
        String password = sysUser.getPassword();
        password = (password != null) ? password : "";
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * 认证成功
     *
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                         Authentication authResult) throws IOException, ServletException {
        // 保存到上下文
        SecurityContextHolder.getContext().setAuthentication(authResult);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
        }
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        // 生成jwt
        String jwt = tokenManager.createToken(authResult.getName());

        // jwt写入response
        response.setContentType("application/json");
        response.setCharacterEncoding(Constants.UTF_8);
        response.setHeader(Constants.HEAD_AUTH, TokenManager.BEARER + jwt);
        HashMap<String, Object> data = new HashMap<>();
        data.put(Constants.TOKEN, jwt);
        response.getWriter().write(JSON.toJSONString(R.ok(data)));

        // 用户权限保存到redis
        redisTemplate.opsForHash().put(Constants.REDIS_KEY_USER_PERM, authResult.getName(), authResult.getAuthorities());
    }

    /**
     * 认证失败
     *
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                           AuthenticationException failed) throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        this.authenticationEntryPoint.commence(request, response, failed);
    }

}
